import {
  BaseError,
  BaseErrorCode,
  cm,
  cmReceiverFormat,
  composeLogger,
  CryptoManager,
  EmailAddress,
  encodeAddress,
  getLogger,
  HostConfig,
  IBuffer,
  loginLogger,
  preferenceManager,
  StringUtil,
  SystemInfoUtil
} from '@gal/base';
import { util } from '@kit.ArkTS';
import { http } from '@kit.NetworkKit';
import { ErrorCode, Want } from '@kit.AbilityKit';
import {
  CmLoginReqData,
  Cm2FaLoginRespObj,
  CmResponseCode,
  Login2FAResult,
  CmLoginRespObj,
  CmDevice,
  CmRespBody
} from './CmBaseData';
import {
  HttpManager,
  ReqInputType, RequestConfig, RequestMethod, RespOutputType } from '@gal/base';
import deviceInfo from '@ohos.deviceInfo';
import url from '@ohos.url';
import { BusinessError } from '@kit.BasicServicesKit';
import { app } from '../app/App';

const CookieKey = "cookie";
const SidKey = "sid";
const logger = getLogger('__coremail');


/**
 * 每层之间的通信
 * 1,抛异常
 * 2,回调
 */

/**
 * CoremailStore本身就是实现IStore的邮件收发协议,同时负责调用http请求.
 * 如果有sid,登录以外的所有接口的requestUrl后面要加上sid.
 * 登录接口会返回cookie,需要保存下来,后续所有接口加上去.
 */
export class CoremailStore {
  config: HostConfig;
  url: string;
  sid?: string;
  httpManager:HttpManager
  constructor(config: HostConfig) {
    this.config = config;
    this.url = config.host;
    this.httpManager = new HttpManager();
  }

  async init() {
   await this.httpManager.loadCookie();
   this.sid = await preferenceManager.fetchString(SidKey);
  }


  async buildParamJson(user: string, password: string, isAuthDevice: boolean = false,
    verifyCode?: string): Promise<CmLoginReqData> {
    let model = SystemInfoUtil.productModel(); // WGR-W19
    let os = SystemInfoUtil.osFullName(); // 2.1.6.1(Beta1)
    let osLanguage = SystemInfoUtil.getSystemLanguage(); // zh-Hans
    let deviceType = SystemInfoUtil.getAppName(); // appName
    let friendlyName = SystemInfoUtil.deviceType(); // tablet

    let preferencesManager = cm.CacheManager.sharedInstance();
    let deviceId = preferencesManager.fetchString(cm.DEVICE_ID);
    let nonce = preferencesManager.fetchString(cm.NONCE);
    let accessToken = preferencesManager.fetchString(cm.ACCESS_TOKEN);
    let accessSecret = preferencesManager.fetchString(cm.ACCESS_SECRET);

    if (!deviceId) {
      // deviceId = util.generateRandomUUID();
      deviceId = deviceInfo.ODID
      preferencesManager.save(cm.DEVICE_ID, deviceId);
    }
    loginLogger.debug(`[device id is: ${deviceId}]`);
    if (!accessToken) {
      accessToken = "!L-HMOS:" + deviceId;
      preferencesManager.save(cm.ACCESS_TOKEN, accessToken);
    }
    if (!accessSecret) {
      accessSecret = await CryptoManager.md5String(deviceId);
      preferencesManager.save(cm.ACCESS_SECRET, accessSecret);
    }

    let device: CmDevice = {
      "uuid": deviceId,
      "model": model,
      "os": os,
      "osLanguage": osLanguage,
      "deviceType": deviceType,
      "friendlyName": friendlyName
    }

    let body = {
      uid: user,
      password: password,
      supportSms: 0,
      supportDynamicPwd: true,
      supportBind2FA: true,
      authDevice: isAuthDevice,
      device: device
    }

    if (verifyCode) {
      body["verifyCode"] = verifyCode;
    }
    let signature: string;
    let timestamp = (new Date().getTime() / 1000).toFixed(0);
    if (nonce && nonce.length > 0) {
      body["nonce"] = nonce;
      signature =
        `accessToken=${encodeURIComponent(accessToken)}&nonce=${encodeURIComponent(nonce)}&timestamp=${timestamp}&secret=${encodeURIComponent(accessSecret)}`
    } else {
      signature =
        `accessToken=${encodeURIComponent(accessToken)}&timestamp=${timestamp}&secret=${encodeURIComponent(accessSecret)}`
    }
    signature = signature.replace("!", "%21");
    logger.info("sys.out signature:" + signature);
    let cryptoData = await CryptoManager.cryptoU8a(StringUtil.stringToU8aWithUtf8(signature), "SHA1");
    body["signature"] = StringUtil.uint8ArrayToBase64string(cryptoData);
    body["accessSecret"] = accessSecret;
    body["accessToken"] = accessToken;
    body["timestamp"] = timestamp;
    return body;
  }

  /**
   *账号密码登录.
   *"https://mt.icoremail.net/coremail/s/json?func=user:login"
   * 什么情况下报 FA_UNAUTHORIZED ?
   "code":"FA_UNAUTHORIZED",
   "messages":[{
   "severity":"ERROR",
   "summary":"Authentication Failed!"}]}
   * @param user
   * @param password
   */
  async login(authDevice: boolean = false): Promise<CmRespBody<CmLoginRespObj>> {
    logger.info("login");
    let account = this.config.account;
    let body = await this.buildParamJson(this.config.account, this.config.password, authDevice);
    //设置url
    let loginUrl = `${this.url}/coremail/s/json/?func=user:login`;
    if (this.sid) {
      loginUrl += `&sid=${this.sid}`;
    }
    //设置请求头
    const headers = {
      "content-type": "text/x-json",
      "accept-encoding": "gzip, deflate",
    }
    //填充请求体,具体转换在下一层.

    let config: RequestConfig<CmLoginReqData> = {
      url: loginUrl,
      method: RequestMethod.POST,
      headers: headers,
      body: body,
      reqInputType: ReqInputType.Raw,
      respOutputType: RespOutputType.JsonToObj
    }
    let obj = await this.httpManager.communicate<CmLoginReqData, CmRespBody<CmLoginRespObj>>(config) as CmRespBody<CmLoginRespObj>;
    let sid: string = obj.var.sid;
    loginLogger.debug(`[after login][sid is: ${sid}]`);
    switch (obj.code) {
    //需要动态验证码,怎么处理这个动态验证码?
      case CmResponseCode.FA_NEED_DYNAMIC_PWD: {
        loginLogger.debug(`[code is: FA_NEED_DYNAMIC_PWD]`);
      }
      case CmResponseCode.FA_NEED_VERIFY_EMAIL:
      case CmResponseCode.FA_NEED_VERIFY_CELL: {
        //需要跳转到webview,webview正常流程会回调给一个新的sid,用这个sid再进行验证.
        //1 goto网页,通过加载网页,触发一系列逻辑,最终返回二次验证的结果.
        //authInterfaceUrl代表二次验证的接口的地址.
        const authInterfaceUrl = `${this.url}/coremail/verify2FA`;
        const urlObj = url.URL.parseURL(this.url)
        const returnUrlPrefix = `${urlObj.protocol}//xloginback.${urlObj.host}.xsfx/lunkr`;
        const params = new url.URLParams({
          sid: sid,
          returnUrl: `${returnUrlPrefix}?sid=$sid`,
        });

        //authUrl 代表带上了所有参数的完备的请求的url.
        let authRequestUrl = `${authInterfaceUrl}?${params.toString()}`
        let login2FAResult = await this.login2FA(authRequestUrl, returnUrlPrefix)
        let login2FaSid = login2FAResult.sid;
        loginLogger.debug(`[sid after 2fa is: ${login2FaSid}]`);
        this.setSid(login2FaSid);
        let loginResult = await this.login(login2FAResult.isAuthDevice);
        return loginResult
        break;
      }
      case CmResponseCode.FA_NEED_VERIFY_CODE:
      case CmResponseCode.FA_INVALID_VERIFY_CODE: {
        // sid = obj.var['sid'];
        // this.setSid(sid);
        // let verifyCodeUrl = `${this.url}/coremail/s/json?func=user:generateVerifyCodeImage&sid=${sid}`;
        // let code = await new Promise<string>((resolve, reject) => {
        //   options.promptVerifyCode(verifyCodeUrl, (code: string) => {
        //     resolve(code);
        //     return true;
        //   })
        // })
        // body = await this.buildParamJson(user, password, false, code);
        // obj = await this.call("user:login", body);
        break;
      }
      case CmResponseCode.S_OK: {
        sid = obj.var['sid'] as string
        let nonce = obj.var['nonce'] as string;
        loginLogger.debug(`[password login][success][sid is: ${sid}]`);
        cm.CacheManager.sharedInstance().save(cm.NONCE, nonce);
        break;
      }
      case CmResponseCode.FA_UNAUTHORIZED: {
        //可能是用户名不存在.
        throw new BaseError();
      }
      default: {
        const text = `login failed ${account}`;
        logger.error(text);
        const error = new BaseError(text, BaseErrorCode.LOGIN_FAILED);
        return Promise.reject(error);
      }
    }

    //sid store 层保管,用于构建url.
    this.setSid(sid);
    preferenceManager.saveString(SidKey,sid);
    // cookie httpManager保管用于...
    this.setCookie("Coremail.sid", sid);
    return obj;
  }

  // test() {
  //   deviceInfo.ODID
  // }


  /**
   * 动态验证码的登录
   * @param sid
   * @param prompt2ndAuth
   * @returns
   */
  async login2FA(authRequestUrl: string, returnUrlPrefix: string): Promise<Login2FAResult> {
    // const authUrl = `${this.url}/coremail/verify2FA`;
    // const urlObj = url.URL.parseURL(this.url)
    // const returnUrlPrefix = `${urlObj.protocol}//xloginback.${urlObj.host}.xsfx/lunkr`;
    // const params = new url.URLParams({
    //   sid: sid,
    //   returnUrl: `${returnUrlPrefix}?sid=$sid`,
    // });
    // let result:Login2FAResult = {
    //   sid:"",
    //   isAuthDevice:true
    // }
    // let aurhUrl = ${authUrl}?${params.toString()};
    let result: Login2FAResult = await new Promise((resolve, reject) => {
      app.openAuthWebPage(authRequestUrl);
      //设置回调check
      //authUrl:
      //https://mt.icoremail.net/coremail/verify2FA?sid=BARdjyaaOWNAYQTTXDlxByspwxFparbg&returnUrl=https%3A%2F%2Fxloginback.mt.icoremail.net.xsfx%2Flunkr%3Fsid%3D%24sid
      //redirectUrl:
      //
      app.login2FACheckHandler = (redirectUrl: string) => {
        if (redirectUrl.startsWith(returnUrlPrefix)) {
          const m = redirectUrl.indexOf('?')
          const urlObj = new url.URLParams(redirectUrl.substring(m + 1))
          const sid = urlObj.get('sid')
          const authorizeDevice = urlObj.get('authorizeDevice') === "true"
          if (sid) {
            resolve({ sid: sid, isAuthDevice: authorizeDevice });
            return true;
          }
          throw new BaseError();
        }
        return false;
        // else {
        //   throw new BaseError();
        // }
      }
    })
    return result;
  }


  setCookie(key: string, value: string) {
    this.httpManager.setCookie(key,value);
  }

  async send(html:string) {
    let composeId = await this.initCompose();
    if(!composeId) {
      return;
    }
    composeLogger.debug(`[compose id: ${composeId}]`)
    let from:EmailAddress = {
      name:"GikkiAres",
      email:"ybg@coremail.cn"
    }
    let toList = [from];
    let ccList = [from];
    let bccList = [from];
    let richText = html;
    let body = {
      "id": composeId,
      "attrs": {
        "account": encodeAddress(from),
        "to": toList.map(a => cmReceiverFormat(a)),
        "cc": ccList.map(a => cmReceiverFormat(a)),
        "bcc": bccList.map(a => cmReceiverFormat(a)),
        "subject": "Test01",
        "content": richText,
        "isHtml": true,
        "saveSentCopy": true,
        "attachments": [],
        "ctrls": {}
      },
      "action": "deliver",
      "xMailerExt": "HM-OS(1.0.0)"
    }
    const headers = {
      "content-type": "text/x-json",
      "accept-encoding": "gzip, deflate",
    }
    let url = this.getUrlWithFunName('mbox:compose')
    let config: RequestConfig<object> = {
      url:url ,
      respOutputType: RespOutputType.JsonToObj,
      reqInputType: ReqInputType.ObjToJson,
      body: body,
      headers:headers
    };
    let resp = await this.httpManager.communicate<object, CmRespBody<string>>(config) as CmRespBody<string>;
    if(resp.code == CmResponseCode.S_OK) {
      composeLogger.debug(`[compose success]`)
    }
    else {
      composeLogger.error(`[compose failed]`)
    }
  }

  setSid(sid: string) {
    this.sid = sid;
  }

  // setCookie2(key: string, value: string) {
  //   if (!this.cookie) {
  //     this.cookie = `${key}=${value}`;
  //   } else if (this.cookie.includes(key + "=")) {
  //     this.cookie = this.cookie.replace(
  //       new RegExp(`${key}=[^;]*`),
  //       `${key}=${value}`
  //     );
  //   } else {
  //     this.cookie += `; ${key}=${value}`;
  //   }
  // }


  async sendMail(from: EmailAddress,
    toList: EmailAddress[],
    ccList: EmailAddress[],
    bccList: EmailAddress[],
    subject: string,
    richText: string,
    plainText: string,
    inlineImages: {
      content: IBuffer;
      fileName: string;
      contentId: string
    }[],
    attachments: {
      fileName: string;
      content: IBuffer
    }[]): Promise<void> {

    let composeId = await this.initCompose();
    // let temp = [];
    // if (attachments && attachments.length > 0) {
    //   for (const item of attachments) {
    //     // item.content
    //     let filePath = (item.content as FileBuffer).filePath;
    //     let stat = FileManager.getFileInfo(filePath);
    //     let prepareResult = await this.uploadPrepare(protocol, composeId, -1, item.fileName,
    //       FilePathGenerator.getFileContentType(item.fileName), stat.size);
    //     await this.uploadDirectData(protocol, this._context, composeId, item.fileName, prepareResult.attachmentId,
    //       filePath);
    //     temp.push({
    //       "id": prepareResult.attachmentId,
    //       "type": "upload",
    //       "name": prepareResult.fileName,
    //       "displayName": item.fileName,
    //       "contentType": prepareResult.contentType,
    //       "inlined": false
    //     })
    //   }
    // }
    //
    // if (inlineImages && inlineImages.length > 0) {
    //   for (const item of inlineImages) {
    //
    //     let filePath = (item.content as FileBuffer).filePath;
    //     let stat = FileManager.getFileInfo(filePath);
    //     let prepareResult = await this.uploadPrepare(protocol, composeId, 0, item.fileName,
    //       FilePathGenerator.getFileContentType(item.fileName), stat.size);
    //     await this.uploadDirectData(protocol, this._context, composeId, item.fileName, prepareResult.attachmentId,
    //       filePath);
    //     let imageUrl =
    //       `/coremail/s?func=mbox:getComposeData&amp;sid=${protocol.sid}&amp;composeId=${composeId}&amp;attachId=${prepareResult.attachmentId}`
    //     richText = richText.replace(`cid:${item.contentId}`, imageUrl);
    //     temp.push({
    //       "id": prepareResult.attachmentId,
    //       "type": "upload",
    //       "name": prepareResult.fileName,
    //       "contentType": prepareResult.contentType,
    //       "inlined": true,
    //       "contentId": item.contentId
    //     })
    //   }
    // }
    //
    // function cmReceiverFormat(address: EmailAddress) {
    //   if (!address.name) {
    //     return address.email
    //   }
    //   return `${address.name} <${address.email}>`
    // }
    //
    // let body = {
    //   "id": composeId,
    //   "attrs": {
    //     "account": encodeAddress(from),
    //     "to": toList.map(a => cmReceiverFormat(a)),
    //     "cc": ccList.map(a => cmReceiverFormat(a)),
    //     "bcc": bccList.map(a => cmReceiverFormat(a)),
    //     "subject": subject,
    //     "content": richText,
    //     "isHtml": true,
    //     "saveSentCopy": true,
    //     "attachments": temp,
    //     "ctrls": {}
    //   },
    //   "action": "deliver",
    //   "xMailerExt": "HM-OS(1.0.0)"
    // }
    // await protocol.execute('mbox:compose', body);
  }

  /**
   * ${this.url}/coremail/s/json/?func=user:login
   * @param funName
   * @returns
   */
  getUrlWithFunName(funName: string): string {
    let url = `${this.url}/coremail/s/json/?func=${funName}`
    if(this.sid) {
      url+=`&sid=${this.sid}`;
    }
    return url;
  }
  /**
   * httpManager调用者,负责解析出需要的数据.
   * 接口处理层
   * 如果报错,将错误优雅地传给上层处理
   * 错误的统一处理
   * @returns
   */
  async initCompose() {
    //设置请求头
    const headers = {
      "content-type": "text/x-json",
      "accept-encoding": "gzip, deflate",
    }
    let url = this.getUrlWithFunName('mbox:compose')
    let config: RequestConfig<{action:string}> = {
      url:url ,
      respOutputType: RespOutputType.JsonToObj,
      reqInputType: ReqInputType.ObjToJson,
      body: { "action": "continue" },
      headers:headers
    };
    let resp = await this.httpManager.communicate<{ action:string }, CmRespBody<string>>(config)
      .catch((e:unknown) => {
        let err = e as Error;
        composeLogger.debug(`[initCompose aftter][request failed][${err.message}]`)
        //..错误处理
        throw  new BaseError("",BaseErrorCode.HTTP_REQUEST_ERROR,err.message);
      }) as CmRespBody<string>
    if(resp.code != CmResponseCode.S_OK) {
      composeLogger.debug(`[initCompose aftter][business failed][${resp.code}]`)
      //..错误处理
      throw new BaseError("",BaseErrorCode.HTTP_BUSINESS_ERROR,resp.code);
    }

    let composeId = resp.var;
    composeLogger.debug(`[initCompose after][compose id is: ${composeId}]`)
    return composeId;
  }
}
